/* Copyright (c) CERN
 * * Artistic License 2.0
 * */

import java.util.Random;
import java.lang.Math;
import java.util.Arrays;

/**
 * Haar cosine based pseudo distribution
 *
 * @author Xavier Grehant
 */
public class Cosine extends DistroUtils implements Distro {

/**
 * Parameters of the distribution: values in frequency space
 */
   double[] params;

/**
 * Full approximation with the same number of points as the sample set.
 */
   double[] approximation; 

/**
 * Number of initial values
 */
   int size;

/**
 * A uniform random number generator.
 */
   Random rand;

/**
 * Constructor taking the parameters of the distribution.
 * @param params parameters of the distro.
 */
   public Cosine(double[] params, int size) {
      this.params = params;
      this.size = size;
      rand = new Random();
      this.approximation = IDCT2();
   }

/**
 * Constructor taking a sample set and the number of params.
 * Samples are sorted. It's just a list but imagine that x values go from 0 to 1
 * by steps of 1/N where N is the number of samples.
 * We obtain an approximation of P(X > x).
 * @param array the sample set.
 * @param paramn number of parameters of the pseudo-distribution.
 */
   public Cosine(int[] array, int paramn) {
      this.size = array.length;
      rand = new Random();
      Arrays.sort(array);
      array = flip(array);
      params = FDCT2(array, paramn);
      this.approximation = IDCT2();
   }

/**
 * Integer Array flip
 * @return flipped array.
 * @param array array to flip.
 */
   int[] flip(int[] array) {
      int[] output = new int[array.length];
      for (int k = 0; k < array.length; k++)
         output[array.length - 1 - k] = array[k];
      return output;
   }

/**
 * Forward DCT type II
 * @return the first parameters of the DCT.
 * @param array the samples.
 * @param paramn the number of parameters in the output.
 */
   double[] FDCT2(int[] array, int paramn) {
      double[] output = new double[paramn];
      Arrays.fill(output, 0);
      for (int k = 0; k < paramn; k++)
         for (int n = 0; n < size; n++)
            output[k] += array[n]
                  * Math.cos((Math.PI / size) * (n + .5) * k);
      return output;
   }

/**
 * Single-point inverse DCT type II
 * @return the value of the approximationed function at the specified point,
 *    supposing the function is defined bewteen 0 and 1.
 * @param value the specified point.
 */
   double IDCT2(double value) {
      double sum = 0.;
      for (int k = 1; k < params.length; k++) {
            sum += params[k] * Math.cos(k * value * Math.PI);
         }
      return (2. / size) * ((params[0] / 2) + sum);
   }

/**
 * Inverse DCT type II
 * @return the value of the approximationed function at any point,
 *    supposing the function is defined bewteen 0 and 1.
 */
   double[] IDCT2() {
      double[] output = new double[size];
      for (int n = 0; n < size; n++) {
         double sum = 0.;
         for (int k = 1; k < params.length; k++)
            sum += params[k] * Math.cos((k * Math.PI / size) * (n + .5));
         output[n] = (2. / size) * ((params[0] / 2) + sum);
      }
      return output;
   }

/**
 * Random number generator according to the pseudo-distribution.
 * @return random number according to the pseudo-distribution.
 */
   public double nextDouble() {
      return IDCT2(rand.nextDouble());
   }

/**
 * Cumulative density function of the pseudo-distribution.
 * @return P(X <= x)
 * @param value the point at which the cdf is calculated.
 */
   public double cdf(double value) {
      int n;
      for (n = 0; n < approximation.length; n++) if (approximation[n] <= value) break;
      return 1. - ((double) n) / approximation.length;
   }
}
